#include "utils.h"
#include "buffer.h"
#include <string.h>
#include <stdlib.h>
#include <cmath>

struct FEException {

};

int64_t remainFERange(FERange *range, int64_t bit_offset) {
    return range->bit_len - bit_offset;
}

char *fetchFERange(FERange *range, int64_t bit_offset, int64_t bit_len, uint8_t *bit_first){
    if (bit_offset + bit_len > range->bit_len)
        return nullptr;

    uint64_t fetch_offset = range->bit_offset + bit_offset;
    uint64_t fetch_start = fetch_offset >> 3;
    uint64_t fetch_end = (fetch_offset + bit_len + 0x7) >> 3;
    if (bit_first) *bit_first = fetch_offset & 0x7;
    return fetchFEBuffer((FEBuffer *)range->buffer, fetch_start, fetch_end - fetch_start);
}

int subFERange(FERange *ori, FERange *sub, int64_t bit_offset, int64_t bit_len){
    if (bit_offset >= 0 && bit_len >= 0 && (bit_offset + bit_len) > ori->bit_len)
        return -1;

    sub->buffer = ori->buffer;
    sub->bit_offset = bit_offset >= 0 ? ori->bit_offset + bit_offset : bit_offset;
    sub->bit_len = bit_len;
    return 0;
}

uint8_t decodeFEDataFS(char *data, uint8_t bit_first, uint8_t bit_len, int32_t *value) {
    uint32_t v = 0;

    uint8_t bytes = (bit_first + bit_len + 7) >> 3;
    uint8_t bit_last = (bit_first + bit_len - 1) % 8;

    v = data[bytes - 1] >> (7 - bit_last);
    bytes--;
    bit_last += 1;

    while (bytes) {
        bit_last += 8;
        v |= data[bytes - 1] << bit_last;
        bytes--;
    }

    v <<= bit_first;
    v >>= bit_first;
    *value = v;
    return bit_len;
}

uint8_t decodeFEDataFU(char *data, uint8_t bit_first, uint8_t bit_len, uint32_t *value) {
    uint32_t v = 0;

    uint8_t bytes = (bit_first + bit_len + 7) >> 3;
    uint8_t bit_last = (bit_first + bit_len - 1) % 8;

    v = ((uint8_t)data[bytes - 1]) >> (7 - bit_last);
    bytes--;
    bit_last += 1;

    while (bytes) {
        v |= ((uint8_t)data[bytes - 1]) << bit_last;
        bytes--;
        bit_last += 8;
    }

    v &= 0xffffffff >> (32 - bit_len);
    *value = v;
    return bit_len;
}

uint8_t decodeFEDataVS(char *data, uint8_t bit_first, uint8_t bit_max, FECode code, int32_t *value) {
    uint32_t cache = 0;
    uint8_t bits = bit_max > 32 ? 32 : bit_max;
    decodeFEDataFU(data, bit_first, bits, &cache);
    if (cache == 0) return 0;

    int log = log2(cache);
    if (bits > (2*log + 1)) return 0;

    cache = (cache >> (2 *log + 1 - bits)) - 1;
    int sign = (cache & 1) - 1;
    *value = ((cache >> 1) ^ sign) + 1;
    return 2 * bits - 2 * log - 1;
}

uint8_t decodeFEDataVU(char *data, uint8_t bit_first, uint8_t bit_max, FECode code, uint32_t *value){
    uint32_t cache = 0;
    uint8_t bits = bit_max > 32 ? 32 : bit_max;
    decodeFEDataFU(data, bit_first, bits, &cache);
    // if (cache == 0) return 0;
    if (cache == 0) throw FEException();

    int log = log2(cache);
    if (bits > (2 * log + 1)) return 0;

    *value = (cache >> (2 * log + 1 - bits)) - 1;
    return 2 * bits - 2 * log - 1;
}

void initFEList(FEList *list, int unit_size) {
    list->get = -1;
    list->number = 0;
    list->reserve = 8;
    list->size = unit_size;
    list->units = malloc(list->reserve * list->size);
}

void *getNextFEUnit(FEList *list) {
    if(list->get >= list->number - 1) return nullptr;
    list->get++;
    return (char *)list->units + list->get * list->size;
}

void putbackFEUnit(FEList *list) {
    list->get--;
    return;
}

static void *fetchFEUnit(FEList *list) {
    if (list->number == list->reserve) {
        list->reserve *= 2;
        list->units = realloc(list->units, list->reserve * list->size);
    }

    ++list->number;
    return (char *)list->units + (list->number - 1) * list->size;
}

void clearFEList(FEList *list) {
    if(list->units) free(list->units);
}

int addSubFormat(FEList *list, void *context, const char *index){
    auto format = (FESubFormat *)fetchFEUnit(list);
    format->context = context;
    strcpy_s(format->index, index);
    return 0;
}

FEFieldNode *allocFENode(FEList *nodes, FEField *field, FERange range) {
    auto node = (FEFieldNode *)fetchFEUnit(nodes);
    node->field = field;
    node->range = range;
    node->depth = 0;
    node->context = nullptr;
    node->free = nullptr;
    node->index1 = -1;
    node->index2 = -1;
    node->index3 = -1;
    node->translate_type = FEFieldNode::Raw;
    return node;
}

int depthFENode(FEFieldNode *node, uint8_t depth){
    if (!node) return -1;
    node->depth = depth;
    return 0;
}

int contextFENode(FEFieldNode *node, void *context, FreeContext free){
    if (!node) return -1;
    node->context = context;
    node->free = free;
    return 0;
}

int indexFENode(FEFieldNode *node, int index1, int index2, int index3) {
    if (!node) return -1;
    node->index1 = index1;
    node->index2 = index2;
    node->index3 = index3;
    return 0;
}

int translateFENodeFloat(FEFieldNode *node, double translation) {
    if (!node) return -1;
    node->translate_type = FEFieldNode::ToFloat;
    node->translation.d = translation;
    return 0;
}

int translateFENodeUInt(FEFieldNode *node, uint64_t translation){
    if (!node) return -1;
    node->translate_type = FEFieldNode::ToUInt;
    node->translation.ui = translation;
    return 0;
}

int translateFENodeInt(FEFieldNode *node, int64_t translation) {
    if (!node) return -1;
    node->translate_type = FEFieldNode::ToInt;
    node->translation.i = translation;
    return 0;
}

int translateFENodeText(FEFieldNode *node, char *translation, uint64_t translation_len){
    if (!node) return -1;
    node->translate_type = FEFieldNode::ToText;
    node->translation.t.ptr = translation;
    node->translation.t.len = translation_len;
    return 0;
}

int displayBool(FEFieldNode *node, char *string, int len)
{
    if(node->translate_type != FEFieldNode::ToUInt || node->translation.ui > 1)
        return sprintf(string, "error");

    return sprintf(string, "%s", node->translation.ui ? "True" : "False");
}

int displayHex(FEFieldNode *node, char *string, int len)
{
    if(node->translate_type != FEFieldNode::ToUInt)
        return sprintf(string, "error");

    return sprintf(string, "0x%x", (uint32_t)node->translation.ui);
}

int displayString(FEFieldNode *node, char *string, int len)
{
    auto data = fetchFERange(&node->range, 0, node->range.bit_len, nullptr);
    memcpy(string, data, node->range.bit_len >> 3);
    return node->range.bit_len >> 3;
}
